package visitors;

import nodes.FieldNode;
import nodes.*;
import nodes.body.*;
import nodes.declarations.*;
import nodes.exprnodes.*;
import nodes.exprnodes.bin.*;
import nodes.exprnodes.consts.*;
import nodes.exprnodes.sequences.*;
import nodes.exprnodes.unary.*;
import nodes.exprnodes.variables.*;
import nodes.statements.*;
import nodes.statements.controlflow.*;

public class PrettyPrintVisitor implements Visitor
{
    private int level = 0;
    private int tab = 4;

    private void increaseIndent()
    {
        level++;
    }

    private void decreaseIndent()
    {
        level--;
    }

    private void indent()
    {
        for (int i = 0; i<level*tab; i++)
        {
            System.out.print(' ');
        }
    }

    public Object visit(BoolNode n)
    {
        System.out.print(n.value);
        return null;
    }

    public Object visit(AndNode n)
    {
        System.out.print('(');
        n.left.accept(this);
        System.out.print(" && ");
        n.right.accept(this);
        System.out.print(')');
        return null;
    }

    public Object visit(CharNode n)
    {
        System.out.print('\'' + n.value.toString() + '\'');
        return null;
    }

    public Object visit(ConcatNode n)
    {
        System.out.print('(');
        n.left.accept(this);
        System.out.print(" :: ");
        n.right.accept(this);
        System.out.print(')');
        return null;
    }

    public Object visit(DivideNode n)
    {
        System.out.print('(');
        n.left.accept(this);
        System.out.print(" / ");
        n.right.accept(this);
        System.out.print(')');
        return null;
    }

    public Object visit(FloatNode n)
    {
        System.out.print(n.value);
        return null;
    }

    public Object visit(nodes.exprnodes.FuncCallNode n)
    {
        System.out.print(n.id + '(');
        if (n.params!=null)
        {
            n.params.accept(this);
        }
        System.out.print(')');
        return null;
    }

    public Object visit(GreaterNode n)
    {
        System.out.print('(');
        n.left.accept(this);
        System.out.print(" > ");
        n.right.accept(this);
        System.out.print(')');
        return null;
    }

    public Object visit(GreaterOrEqualNode n)
    {
        System.out.print('(');
        n.left.accept(this);
        System.out.print(" >= ");
        n.right.accept(this);
        System.out.print(')');
        return null;
    }

    public Object visit(IdNode n)
    {
        System.out.print(n.id);
        return null;
    }

    public Object visit(InNode n)
    {
        System.out.print('(');
        n.left.accept(this);
        System.out.print(" in ");
        n.right.accept(this);
        System.out.print(')');
        return null;
    }

    public Object visit(IndexNode n)
    {
        System.out.print(n.id + '[');
        n.expr.accept(this);
        System.out.print(']');
        return null;
    }

    public Object visit(VariableListNode n)
    {
        n.left.accept(this);
        if (n.right!=null)
        {
            System.out.print('.');
            n.right.accept(this);
        }
        return null;
    }

    public Object visit(IntNode n)
    {
        System.out.print(n.value);
        return null;
    }

    public Object visit(LenNode n)
    {
        System.out.print("len(");
        n.expr.accept(this);
        System.out.print(')');
        return null;
    }

    public Object visit(LessNode n)
    {
        System.out.print('(');
        n.left.accept(this);
        System.out.print(" < ");
        n.right.accept(this);
        System.out.print(')');
        return null;
    }
    
    public Object visit(LessOrEqualNode n)
    {
        System.out.print('(');
        n.left.accept(this);
        System.out.print(" <= ");
        n.right.accept(this);
        System.out.print(')');
        return null;
    }

    public Object visit(ListNode n)
    {
        System.out.print('[');
        for (ExprNode cur = n.list; cur!=null; cur = cur.next)
        {
            cur.accept(this);
            if (cur.next!=null)
                System.out.print(", ");
        }
        System.out.print(']');
        return null;
    }

    public Object visit(MinusNode n)
    {
        System.out.print('(');
        n.left.accept(this);
        System.out.print(" - ");
        n.right.accept(this);
        System.out.print(')');
        return null;
    }

    public Object visit(NotEqualNode n)
    {
        System.out.print('(');
        n.left.accept(this);
        System.out.print(" != ");
        n.right.accept(this);
        System.out.print(')');
        return null;
    }

    public Object visit(NotInNode n)
    {
        System.out.print('(');
        n.left.accept(this);
        System.out.print(" not in ");
        n.right.accept(this);
        System.out.print(')');
        return null;
    }

    public Object visit(NotNode n)
    {
        System.out.print("(!");
        n.expr.accept(this);
        System.out.print(')');
        return null;
    }

    public Object visit(OrNode n)
    {
        System.out.print('(');
        n.left.accept(this);
        System.out.print(" || ");
        n.right.accept(this);
        System.out.print(')');
        return null;
    }

    public Object visit(PlusNode n)
    {
        System.out.print('(');
        n.left.accept(this);
        System.out.print(" + ");
        n.right.accept(this);
        System.out.print(')');
        return null;
    }

    public Object visit(PowerNode n)
    {
        System.out.print('(');
        n.left.accept(this);
        System.out.print(" ^ ");
        n.right.accept(this);
        System.out.print(')');
        return null;
    }

    public Object visit(StringNode n)
    {
        System.out.print('\"' + n.value + '\"');
        return null;
    }

    public Object visit(TimesNode n)
    {
        System.out.print('(');
        n.left.accept(this);
        System.out.print(" * ");
        n.right.accept(this);
        System.out.print(')');
        return null;
    }

    public Object visit(TupleNode n)
    {
        System.out.print("[|");
        for (ExprNode cur = n.list; cur!=null; cur = cur.next)
        {
            cur.accept(this);
            if (cur.next!=null)
                System.out.print(", ");
        }
        System.out.print("|]");
        return null;
    }

    public Object visit(BasicDeclarationNode n)
    {
        indent();
        System.out.print(n.id + " : " + n.type);
        if (n.init!=null)
        {
            System.out.print(" = ");
            n.init.accept(this);
        }
        System.out.println(';');
        return null;
    }

    public Object visit(UserDeclarationNode n)
    {
        indent();
        System.out.print(n.id + " : " + n.type);
        if (n.init!=null)
        {
            System.out.print(" = ");
            for (ExprNode cur = n.init; cur!=null; cur = cur.next)
            {
                cur.accept(this);
                if (cur.next!=null)
                    System.out.print(", ");
            }
        }
        System.out.println(';');
        return null;
    }

    public Object visit(DataDeclarationNode n)
    {
        System.out.print("tdef " + n.name + " : ");
        for (FieldNode cur = n.fields; cur!=null; cur = cur.next)
        {
            cur.accept(this);
            if (cur.next!=null)
                System.out.print(", ");
        }
        System.out.println(';');
        return null;
    }

    public Object visit(FunctionDeclarationNode n)
    {
        System.out.print("fdef " + n.id + "(");
        if (n.parameters!=null)
        {
            for (FieldNode cur = n.parameters; cur!=null; cur = cur.next)
            {
                cur.accept(this);
                if (cur.next!=null)
                    System.out.print(", ");
            }
        }
        System.out.print(") : " + n.return_type + ' ');
        increaseIndent();
        n.body.accept(this);
        decreaseIndent();
        return null;
    }

    public Object visit(FieldNode n)
    {
        System.out.print(n.id + ':' + n.type);
        return null;
    }

    public Object visit(ProgramNode n)
    {
        if (n.decl!=null)
        {
            n.decl.accept(this);
        }
        increaseIndent();
        n.body.accept(this);
        decreaseIndent();
        return null;
    }

    public Object visit(EqualNode n)
    {
        System.out.print('(');
        n.left.accept(this);
        System.out.print(" == ");
        n.right.accept(this);
        System.out.print(')');
        return null;
    }

    public Object visit(FunctionBodyNode n)
    {
        System.out.println('{');
        if (n.decl!=null)
        {
            n.decl.accept(this);
        }
        if (n.stm!=null)
        {
            n.stm.accept(this);
        }
        indent();
        System.out.print("return");
        if (n.returnExpr!=null)
        {
            System.out.print(' ');
            n.returnExpr.accept(this);
        }
        System.out.println(';');
        System.out.println('}');
        return null;
    }

    public Object visit(BlockBodyNode n)
    {
        decreaseIndent();
        indent();
        increaseIndent();
        System.out.println('{');
        if (n.decl!=null)
        {
            n.decl.accept(this);
        }
        if (n.stm!=null)
        {
            n.stm.accept(this);
        }
        decreaseIndent();
        indent();
        increaseIndent();
        System.out.println('}');
        return null;
    }

    public Object visit(StatementListNode n)
    {
        n.left.accept(this);
        if (n.right!=null)
        {
            n.right.accept(this);
        }
        return null;
    }

    public Object visit(IfNode n)
    {
        indent();
        System.out.print("if (");
        n.expr.accept(this);
        System.out.println(") ");
        increaseIndent();
        n.body.accept(this);
        decreaseIndent();
        indent();
        System.out.println("else");
        increaseIndent();
        n.elseBody.accept(this);
        decreaseIndent();
        return null;
    }

    public Object visit(nodes.statements.FuncCallNode n)
    {
        indent();
        System.out.print(n.id + '(');
        if (n.params!=null)
        {
            n.params.accept(this);
        }
        System.out.println(");");
        return null;
    }

    public Object visit(RepeatNode n)
    {
        indent();
        System.out.println("repeat ");
        increaseIndent();
        n.body.accept(this);
        decreaseIndent();
        indent();
        System.out.print("until (");
        n.expr.accept(this);
        System.out.println(");");
        return null;
    }

    public Object visit(WhileNode n)
    {
        indent();
        System.out.print("while (");
        n.expr.accept(this);
        System.out.println(") do ");
        increaseIndent();
        n.body.accept(this);
        decreaseIndent();
        return null;
    }

    public Object visit(AssignNode n)
    {
        indent();
        n.var.accept(this);
        System.out.print(" = ");
        n.expr.accept(this);
        System.out.println(';');
        return null;
    }

    public Object visit(DeclarationListNode n)
    {
        if (n.right!=null)
        {
            n.right.accept(this);
        }
        if (n.left!=null)
        {
            n.left.accept(this);
        }
        return null;
    }
}